// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: Copyright OpenBMC Authors
#pragma once

#include "app.hpp"
#include "async_resp.hpp"
#include "dbus_singleton.hpp"
#include "dbus_utility.hpp"
#include "error_messages.hpp"
#include "http_request.hpp"
#include "logging.hpp"
#include "query.hpp"
#include "registries/privilege_registry.hpp"
#include "utils/chassis_utils.hpp"
#include "utils/sensor_utils.hpp"

#include <boost/beast/http/field.hpp>
#include <boost/beast/http/verb.hpp>
#include <boost/url/format.hpp>
#include <nlohmann/json.hpp>
#include <sdbusplus/asio/property.hpp>

#include <array>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>

namespace redfish
{

inline void afterGetPowerWatts(
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
    const std::string& chassisId, const std::string& path,
    const boost::system::error_code& ec,
    const dbus::utility::DBusPropertiesMap& valuesDict)
{
    if (ec)
    {
        if (ec != boost::system::errc::io_error)
        {
            BMCWEB_LOG_ERROR("DBUS response error for PowerWatts {}", ec);
            messages::internalError(asyncResp->res);
        }
        return;
    }

    nlohmann::json item = nlohmann::json::object();

    /* Don't return an error for a failure to fill in properties from the
     * single sensor. Just skip adding it.
     */
    if (sensor_utils::objectExcerptToJson(
            path, chassisId,
            sensor_utils::ChassisSubNode::environmentMetricsNode, "power",
            valuesDict, item))
    {
        asyncResp->res.jsonValue["PowerWatts"] = std::move(item);
    }
}

inline void handleTotalPowerList(
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
    const std::string& chassisId, const boost::system::error_code& ec,
    const std::shared_ptr<sensor_utils::SensorServicePathList>& sensorList)
{
    BMCWEB_LOG_DEBUG("handleTotalPowerList: {}", sensorList->size());

    if (ec)
    {
        if (ec != boost::system::errc::io_error)
        {
            BMCWEB_LOG_ERROR("D-Bus response error {}", ec);
            messages::internalError(asyncResp->res);
        }
        return;
    }

    // TotalPower cannot be supplied by multiple sensors
    if (sensorList->size() != 1)
    {
        if (sensorList->empty())
        {
            // None found, not an error
            return;
        }
        BMCWEB_LOG_ERROR("Too many total power sensors found {}. Expected 1.",
                         sensorList->size());
        messages::internalError(asyncResp->res);
        return;
    }

    const std::string& serviceName = (*sensorList)[0].first;
    const std::string& sensorPath = (*sensorList)[0].second;
    sdbusplus::asio::getAllProperties(
        *crow::connections::systemBus, serviceName, sensorPath,
        "xyz.openbmc_project.Sensor.Value",
        [asyncResp, chassisId,
         sensorPath](const boost::system::error_code& ec1,
                     const dbus::utility::DBusPropertiesMap& propertiesList) {
            afterGetPowerWatts(asyncResp, chassisId, sensorPath, ec1,
                               propertiesList);
        });
}

inline void getTotalPowerSensor(
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
    const std::string& chassisId, const boost::system::error_code& ec,
    const sensor_utils::SensorServicePathList& sensorsServiceAndPath)
{
    BMCWEB_LOG_DEBUG("getTotalPowerSensor {}", sensorsServiceAndPath.size());

    if (ec)
    {
        if (ec != boost::system::errc::io_error)
        {
            BMCWEB_LOG_ERROR("DBUS response error {}", ec);
            messages::internalError(asyncResp->res);
        }
        // None found, not an error
        return;
    }

    if (sensorsServiceAndPath.empty())
    {
        // No power sensors implement Sensor.Purpose, not an error
        return;
    }

    // Create vector to hold list of sensors with totalPower purpose
    std::shared_ptr<sensor_utils::SensorServicePathList> sensorList =
        std::make_shared<sensor_utils::SensorServicePathList>();

    sensor_utils::getSensorsByPurpose(
        asyncResp, sensorsServiceAndPath,
        sensor_utils::SensorPurpose::totalPower, sensorList,
        std::bind_front(handleTotalPowerList, asyncResp, chassisId));
}

/**
 * @brief Find sensor providing totalPower and fill in response
 *
 * Multiple D-Bus calls are needed to find the sensor providing the totalPower
 * details:
 *
 * 1. Retrieve list of power sensors associated with specified chassis which
 * implement the Sensor.Purpose interface.
 *
 * 2. For each of those power sensors retrieve the actual purpose of the sensor
 * to find the sensor implementing totalPower purpose. Expect no more than
 * one sensor to implement this purpose.
 *
 * 3. If a totalPower sensor is found then retrieve its properties to fill in
 * PowerWatts in the response.
 *
 * @param asyncResp Response data
 * @param validChassisPath Path to chassis, caller confirms path is valid
 * @param chassisId Chassis id matching <validChassisPath>
 */
inline void getPowerWatts(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
                          const std::string& validChassisPath,
                          const std::string& chassisId)
{
    BMCWEB_LOG_DEBUG("getPowerWatts: {}", validChassisPath);

    constexpr std::array<std::string_view, 1> interfaces = {
        "xyz.openbmc_project.Sensor.Purpose"};
    sensor_utils::getAllSensorObjects(
        validChassisPath, "/xyz/openbmc_project/sensors/power", interfaces, 1,
        std::bind_front(getTotalPowerSensor, asyncResp, chassisId));
}

inline void handleEnvironmentMetricsHead(
    App& app, const crow::Request& req,
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
    const std::string& chassisId)
{
    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
    {
        return;
    }

    auto respHandler = [asyncResp, chassisId](
                           const std::optional<std::string>& validChassisPath) {
        if (!validChassisPath)
        {
            messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
            return;
        }

        asyncResp->res.addHeader(
            boost::beast::http::field::link,
            "</redfish/v1/JsonSchemas/EnvironmentMetrics/EnvironmentMetrics.json>; rel=describedby");
    };

    redfish::chassis_utils::getValidChassisPath(asyncResp, chassisId,
                                                std::move(respHandler));
}

inline void doEnvironmentMetricsGet(
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
    const std::string& chassisId,
    const std::optional<std::string>& validChassisPath)
{
    if (!validChassisPath)
    {
        messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
        return;
    }

    asyncResp->res.addHeader(
        boost::beast::http::field::link,
        "</redfish/v1/JsonSchemas/EnvironmentMetrics/EnvironmentMetrics.json>; rel=describedby");
    asyncResp->res.jsonValue["@odata.type"] =
        "#EnvironmentMetrics.v1_3_0.EnvironmentMetrics";
    asyncResp->res.jsonValue["Name"] = "Chassis Environment Metrics";
    asyncResp->res.jsonValue["Id"] = "EnvironmentMetrics";
    asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
        "/redfish/v1/Chassis/{}/EnvironmentMetrics", chassisId);

    getPowerWatts(asyncResp, *validChassisPath, chassisId);
}

inline void handleEnvironmentMetricsGet(
    App& app, const crow::Request& req,
    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
    const std::string& chassisId)
{
    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
    {
        return;
    }

    redfish::chassis_utils::getValidChassisPath(
        asyncResp, chassisId,
        std::bind_front(doEnvironmentMetricsGet, asyncResp, chassisId));
}

inline void requestRoutesEnvironmentMetrics(App& app)
{
    BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/EnvironmentMetrics/")
        .privileges(redfish::privileges::headEnvironmentMetrics)
        .methods(boost::beast::http::verb::head)(
            std::bind_front(handleEnvironmentMetricsHead, std::ref(app)));

    BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/EnvironmentMetrics/")
        .privileges(redfish::privileges::getEnvironmentMetrics)
        .methods(boost::beast::http::verb::get)(
            std::bind_front(handleEnvironmentMetricsGet, std::ref(app)));
}

} // namespace redfish
